/*
 * 'bcrypt' password hash function, for PuTTY's import/export of
 * OpenSSH encrypted private key files.
 *
 * This is not really the same as the original bcrypt; OpenSSH has
 * modified it in various ways, and of course we have to do the same.
 */

#include <stddef.h>
#include <string.h>
#include "ssh.h"
#include "sshblowf.h"

BlowfishContext *bcrypt_setup(const unsigned char *key,
                              int keybytes,
                              const unsigned char *salt,
                              int saltbytes)
{
  int i;
  BlowfishContext *ctx;

  ctx = blowfish_make_context();
  blowfish_initkey(ctx);
  blowfish_expandkey(ctx, key, keybytes, salt, saltbytes);

  /* Original bcrypt replaces this fixed loop count with the
   * variable cost. OpenSSH instead iterates the whole thing more
   * than once if it wants extra rounds. */
  for (i = 0; i < 64; i++) {
    blowfish_expandkey(ctx, salt, saltbytes, NULL, 0);
    blowfish_expandkey(ctx, key, keybytes, NULL, 0);
  }

  return ctx;
}

void bcrypt_hash(const unsigned char *key,
                 int keybytes,
                 const unsigned char *salt,
                 int saltbytes,
                 unsigned char output[32])
{
  BlowfishContext *ctx;
  int i;

  ctx = bcrypt_setup(key, keybytes, salt, saltbytes);
  /* This was quite a nice starting string until it ran into
   * little-endian Blowfish :-/ */
  memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32);
  for (i = 0; i < 64; i++) {
    blowfish_lsb_encrypt_ecb(output, 32, ctx);
  }
  blowfish_free_context(ctx);
}

void bcrypt_genblock(int counter,
                     const unsigned char hashed_passphrase[64],
                     const unsigned char *salt,
                     int saltbytes,
                     unsigned char output[32])
{
  SHA512_State shastate;
  unsigned char hashed_salt[64];
  unsigned char countbuf[4];

  /* Hash the input salt with the counter value optionally suffixed
   * to get our real 32-byte salt */
  SHA512_Init(&shastate);
  SHA512_Bytes(&shastate, salt, saltbytes);
  if (counter) {
    PUT_32BIT_MSB_FIRST(countbuf, counter);
    SHA512_Bytes(&shastate, countbuf, 4);
  }
  SHA512_Final(&shastate, hashed_salt);

  bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output);

  smemclr(&shastate, sizeof(shastate));
  smemclr(&hashed_salt, sizeof(hashed_salt));
}

void openssh_bcrypt(const char *passphrase,
                    const unsigned char *salt,
                    int saltbytes,
                    int rounds,
                    unsigned char *out,
                    int outbytes)
{
  unsigned char hashed_passphrase[64];
  unsigned char block[32], outblock[32];
  const unsigned char *thissalt;
  int thissaltbytes;
  int modulus, residue, i, j, round;

  /* Hash the passphrase to get the bcrypt key material */
  SHA512_Simple(passphrase, strlen(passphrase), hashed_passphrase);

  /* We output key bytes in a scattered fashion to meld all output
   * key blocks into all parts of the output. To do this, we pick a
   * modulus, and we output the key bytes to indices of out[] in the
   * following order: first the indices that are multiples of the
   * modulus, then the ones congruent to 1 mod modulus, etc. Each of
   * those passes consumes exactly one block output from
   * bcrypt_genblock, so we must pick a modulus large enough that at
   * most 32 bytes are used in the pass. */
  modulus = (outbytes + 31) / 32;

  for (residue = 0; residue < modulus; residue++) {
    /* Our output block of data is the XOR of all blocks generated
     * by bcrypt in the following loop */
    memset(outblock, 0, sizeof(outblock));

    thissalt = salt;
    thissaltbytes = saltbytes;
    for (round = 0; round < rounds; round++) {
      bcrypt_genblock(round == 0 ? residue + 1 : 0,
                      hashed_passphrase,
                      thissalt,
                      thissaltbytes,
                      block);
      /* Each subsequent bcrypt call reuses the previous one's
       * output as its salt */
      thissalt = block;
      thissaltbytes = 32;

      for (i = 0; i < 32; i++)
        outblock[i] ^= block[i];
    }

    for (i = residue, j = 0; i < outbytes; i += modulus, j++)
      out[i] = outblock[j];
  }
  smemclr(&hashed_passphrase, sizeof(hashed_passphrase));
}
